New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure reload
sets correct owner for each association (ActiveRecord::Persistence)
#46383
Ensure reload
sets correct owner for each association (ActiveRecord::Persistence)
#46383
Conversation
@eileencodes, may I ask you to review please (since you were adding association cache line before)? Thanks! |
@@ -971,6 +971,7 @@ def reload(options = nil) | |||
end | |||
|
|||
@association_cache = fresh_object.instance_variable_get(:@association_cache) | |||
@association_cache.each_value { |association| association.instance_variable_set(:@owner, self) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what core team folks think on that but I always feel uncomfortable about using instance_variable_set
unless we are patching some code we have no access to. Otherwise we are using something that wasn't supposed to be used even within the framework itself and makes me think whether we should add a public method to association
to be able to set the owner
. To be fair it might not be a solid argument as we already set and access instance variables on the lines above and below this one 🙃
Other than that, the PR makes sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this was exactly my thinking!
I am also not very comfortable using instance_variable_get/instance_variable_set
but in this particular case I decided to go with it because:
- This is already done one line above and below just as you said :)
- I wasn't completely sure my approach would be accepted (dunno, maybe some other reasons I am not aware of) so doing some major change, like trying to introduce some new public method into
association
(which in turn might require changes to the docs, etc, etc) so that it can be used in this place, seemed a bit too premature to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you think it's worth it, let me know, and I will try to introduce my change via a new public method.
However the lines above and below will still be using instance_variable_get
, so dunno... Changing those in this PR as well seems like a bit too much for such a small problem/PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say feel free to leave it as is for now, your reasoning makes total sense 👍 I guess I would have done the same to avoid prematurely overcomplicating the proposal 🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's avoid those instance variable set here. If we need to set the owner outside of that object, we should expose a writter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, updated.
Hey @nvasilevski! |
Please don't ping individuals for review unless they have asked you to. The best way to get feedback would be asking in the Discord |
Oh, wow, there is also a Discord! Thanks, I will try that. |
88d1737
to
70ef26f
Compare
This PR is still relevant for Rails 7.1.1, the issue is still there. |
# The test case below is aimed to fix a bug that was noticeable only with a very specific | ||
# setup of models. The models are fully defined inside the test case so that they are not | ||
# accidentally changed later causing the bug effects to disappear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this test to where it makes sense. You can define new models by creating new classes in the test/models folder, no need to do what is being done here.
Those models here still leaks to all the other files, so you aren't really isolating those models from other tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, updated as well.
@@ -971,6 +971,7 @@ def reload(options = nil) | |||
end | |||
|
|||
@association_cache = fresh_object.instance_variable_get(:@association_cache) | |||
@association_cache.each_value { |association| association.instance_variable_set(:@owner, self) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, let's avoid those instance variable set here. If we need to set the owner outside of that object, we should expose a writter.
Fix rails#46363. Currently calling `reload` re-calculates the association cache based on the object freshly loaded from the database. The fresh object gets assigned as `owner` for each association in the association cache. That object however is not the same as `self` (even though they both point to the same record). Under some specific circumstances (using `has_one/has_many through` associations and transactions with `after_commit` callbacks) this might lead to losing the effects of assignments done inside the callback. This commit fixes it by making `reload` point to `self` as `owner` for each association in the association cache.
70ef26f
to
781ecc2
Compare
…ad-association-cache Ensure `reload` sets correct owner for each association (ActiveRecord::Persistence)
Motivation / Background
Fixes #46363 which was caused by #41112
Currently calling
reload
re-calculates the association cache based on the object freshly loaded from the database. The fresh object gets assigned asowner
for each association in the association cache. That object however is not the same asself
(even though they both point to the same record). Under some specific circumstances (usinghas_one/has_many through
associations and transactions withafter_commit
callbacks) this might lead to losing the effects of assignments done inside the callback.This commit fixes it by making
reload
point toself
asowner
for each association in the association cache.I think this is safe because as far as I can see in https://github.com/rails/rails/blob/main/activerecord/lib/active_record/associations.rb#L320 it always passes
self
when creating a new association.Detail
This Pull Request changes
ActiveRecord::Persistence#reload
method.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]